package com.zzl.alligator.framework.aspectj.log;

import com.zzl.alligator.common.enums.BusinessStatusEnum;
import com.zzl.alligator.common.util.AddressUtils;
import com.zzl.alligator.common.util.IpUtils;
import com.zzl.alligator.common.util.JsonUtils;
import com.zzl.alligator.common.util.ServletUtils;
import com.zzl.alligator.common.util.SpringUtils;
import com.zzl.alligator.common.util.StringUtils;
import com.zzl.alligator.framework.aspectj.annotation.Log;
import com.zzl.alligator.framework.async.AsyncManager;
import com.zzl.alligator.framework.security.LoginUser;
import com.zzl.alligator.framework.security.service.TokenService;
import com.zzl.alligator.management.system.domain.SysOperLog;
import com.zzl.alligator.management.system.service.ISysOperLogService;
import com.zzl.alligator.management.system.service.impl.SysOperLogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.TimerTask;

/**
 * 日志拦截器实现
 *
 * @author zhouziliang
 * @since 2020/7/6
 */
@Aspect
@Component
@Slf4j
public class LogAspect {

    @Autowired
    private TokenService tokenService;

    @Autowired
    private ISysOperLogService operLogService;

    @Pointcut("@annotation(com.zzl.alligator.framework.aspectj.annotation.Log)")
    public void logPointCut() {
    }

    /**
     * 方法执行后执行日志记录
     *
     * @param joinPoint 切入点
     * @param jsonResult 方法返回结果
     */
    @AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
    public void doAfterReturn(JoinPoint joinPoint, Object jsonResult) {
        log.info("开始记录操作日志");
        asyncHanldLog(joinPoint, jsonResult, null);

    }

    @AfterThrowing(pointcut = "logPointCut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        log.info("方法调用异常，记录异常日志");
        asyncHanldLog(joinPoint, null, e);
    }

    private void asyncHanldLog(JoinPoint joinPoint, Object jsonResult, Throwable e) {
        Log logInfo = getLogAnnotation(joinPoint);
        if (logInfo == null) {
            return;
        }
        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
        String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest());

        SysOperLog sysOperLog = new SysOperLog();
        sysOperLog.setOperIp(ipAddr);
        sysOperLog.setBusinessType(logInfo.businessType().ordinal());
        sysOperLog.setTitle(logInfo.title());
        sysOperLog.setOperatorType(logInfo.operatorType().ordinal());
        if (logInfo.isSaveRequestData()) {
            setRquestParameter(joinPoint, sysOperLog);
        }

        sysOperLog.setStatus(BusinessStatusEnum.SUCCESS.ordinal());
        sysOperLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
        String methodName = joinPoint.getSignature().getName();
        String clazzName = joinPoint.getTarget().getClass().getName();
        Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
        StringBuilder sb = new StringBuilder();
        sb.append(clazzName).append(".").append(methodName);
        sb.append("(");
        for(Class param : parameterTypes) {
            sb.append(param.getName());
            sb.append(",");
        }
        trimStringBuiler(sb);
        sb.append(")");
        sysOperLog.setMethod(sb.toString());
        sysOperLog.setRequestMethod(ServletUtils.getRequest().getMethod());
        sysOperLog.setOperLocation(AddressUtils.getRealAddressByIp(ipAddr));

        if (loginUser != null) {
            sysOperLog.setOperName(loginUser.getUsername());

        }
        if (jsonResult != null) {
            sysOperLog.setJsonResult(StringUtils.substring(JsonUtils.toJsonString(jsonResult), 0,2000));
        }
        if (e != null) {
            sysOperLog.setStatus(BusinessStatusEnum.FAIL.ordinal());
            sysOperLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
        }

        sysOperLog.setOperTime(LocalDateTime.now());
        AsyncManager.me().execute(new TimerTask() {
            @Override
            public void run() {
                SysOperLogService service = SpringUtils.getBean(SysOperLogService.class);
                service.insertSysOperLog(sysOperLog);
            }
        });
    }

    private void setRquestParameter(JoinPoint joinPoint, SysOperLog sysOperLog) {
        String method = ServletUtils.getRequest().getMethod();
        if(HttpMethod.POST.name().equals(method) || HttpMethod.PUT.name().equals(method)) {
            Object[] args = joinPoint.getArgs();
            if (args == null || args.length == 0) {
                return;
            }
            StringBuilder sb = new StringBuilder();
            for (Object arg : args) {
                if (!isFilterObject(arg)) {
                    sb.append(JsonUtils.toJsonString(arg));
                    sb.append(",");
                }
            }
            trimStringBuiler(sb);
            sysOperLog.setOperParam(StringUtils.substring(sb.toString(), 0, 2000));
        } else {
            Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            sysOperLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));

        }
    }

    private void trimStringBuiler(StringBuilder sb) {
        if(sb == null || sb.length() < 2 ){
            return;
        }
        if(sb.charAt(sb.length() - 1) == ',') {
            sb.setLength(sb.length() - 1);
            return;
        }
        if(sb.charAt(sb.length() - 2) == ',') {
            sb.setLength(sb.length() - 2);
            return;
        }

    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    public boolean isFilterObject(final Object o) {
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse;
    }

    private Log getLogAnnotation(JoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        if(method != null) {
            return method.getDeclaredAnnotation(Log.class);
        }
        return null;
    }
}
